#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Text;

namespace Osh.ProgDvb
{
	/// <summary>
	/// Summary description for ChannelId.
	/// </summary>
	public struct ChannelId : IComparable
	{
		public static readonly ChannelId Invalid;
		private const int DeviceIdOffset = 16;
		private const int SizeWithoutDeviceId = 20;
		public const int Size = SizeWithoutDeviceId + DeviceId.Size;
		public const int StringSize = SizeWithoutDeviceId*2 + 8 + DeviceId.StringSize;

		private ushort _tid;
		private ushort _sid;
		private ushort _satPosition;
		private ushort _lnb;
		private ushort _nid;
		private ushort _pcr;
		private uint _frequency;
		private DeviceId _deviceId;
		private uint _multicastGroupIP;

		public ChannelId(string channelId)
		{
			if(channelId == null)
				throw new ArgumentNullException("channelId");
			if(channelId.Length != StringSize)
				throw new ArgumentOutOfRangeException("channelId");
			
			int offset = 0;
			_tid = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_sid = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_satPosition = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_lnb = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_nid = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_pcr = HexConverter.ToUInt16(channelId, offset); offset += 5;
			_frequency = HexConverter.ToUInt32(channelId, offset); offset += 9;
			_deviceId = new DeviceId(channelId.Substring(offset, DeviceId.StringSize)); offset += DeviceId.StringSize + 1;
			_multicastGroupIP = HexConverter.ToUInt32(channelId, offset); offset += 9;
		}

		/// <summary>
		/// Creates channel ID from an array of channel ID bytes.
		/// </summary>
		/// <param name="data">Bytes of the full channel ID including DeviceID and Multicast IP.</param>
		public ChannelId(byte[] data)
		{
			int offset = 0;
			_tid = BitConverter.ToUInt16(data, offset); offset += 2;
			_sid = BitConverter.ToUInt16(data, offset); offset += 2;
			_satPosition = BitConverter.ToUInt16(data, offset); offset += 2;
			_lnb = BitConverter.ToUInt16(data, offset); offset += 2;
			_nid = BitConverter.ToUInt16(data, offset); offset += 2;
			_pcr = BitConverter.ToUInt16(data, offset); offset += 2;
			_frequency = BitConverter.ToUInt32(data, offset); offset += 4;
			_deviceId = new DeviceId(data, offset); offset += DeviceId.Size;
			_multicastGroupIP = BitConverter.ToUInt32(data, offset); offset += 4;
		}

		public ChannelId(ushort tid, ushort sid, ushort satPosition, ushort lnb,
			ushort nid, ushort pcr, uint frequency, DeviceId deviceId, uint multicastGroupIP)
		{
			_tid = tid;
			_sid = sid;
			_satPosition = satPosition;
			_lnb = lnb;
			_nid = nid;
			_pcr = pcr;
			_frequency = frequency;
			_deviceId = deviceId;
			_multicastGroupIP = multicastGroupIP;
		}

		public ushort TransportId
		{
			get { return _tid; }
			set { _tid = value; }
		}
		public ushort NetworkId
		{
			get { return _nid; }
			set { _nid = value; }
		}
		public ushort ServiceId
		{
			get { return _sid; }
			set { _sid = value; }
		}
		public ushort SatPosition
		{
			get { return _satPosition; }
			set { _satPosition = value; }
		}
		public ushort Lnb
		{
			get { return _lnb; }
			set { _lnb = value; }
		}
		public ushort Pcr
		{
			get { return _pcr; }
			set { _pcr = value; }
		}
		public uint Frequency
		{
			get { return _frequency; }
			set { _frequency = value; }
		}
		public DeviceId DeviceId
		{
			get { return _deviceId; }
			set { _deviceId = value; }
		}
		public uint MulticastGroupIP
		{
			get { return _multicastGroupIP; }
			set { _multicastGroupIP = value; }
		}

		public override string ToString()
		{
			char[] chars = new char[StringSize];
			int position = 0;
			HexConverter.ToHex(_tid, chars, position); position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_sid, chars, position);  position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_satPosition, chars, position); position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_lnb, chars, position); position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_nid, chars, position); position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_pcr, chars, position); position += 4;
			chars[position++] = '-';
			HexConverter.ToHex(_frequency, chars, position); position += 8;
			chars[position++] = '-';
			_deviceId.ToString().CopyTo(0, chars, position, DeviceId.StringSize); position += DeviceId.StringSize;
			chars[position++] = '-';
			HexConverter.ToHex(_multicastGroupIP, chars, position); position += 8;
			
			return new String(chars);
		}

		public static bool TryParse(string str, out ChannelId channelId)
		{
			channelId = ChannelId.Invalid;
			try
			{
				channelId = new ChannelId(str);
				return true;
			}
			catch(Exception)
			{
				return false;
			}
		}
		
		#region IComparable Members
		public int CompareTo(object obj)
		{
			ChannelId otherId = (ChannelId)obj;
			if(this._tid > otherId._tid) return 1;
			if(this._tid < otherId._tid) return -1;
			if(this._sid > otherId._sid) return 1;
			if(this._sid < otherId._sid) return -1;
			if(this._satPosition > otherId._satPosition) return 1;
			if(this._satPosition < otherId._satPosition) return -1;
			if(this._lnb > otherId._lnb) return 1;
			if(this._lnb < otherId._lnb) return -1;
			if(this._nid > otherId._nid) return 1;
			if(this._nid < otherId._nid) return -1;
			if(this._pcr > otherId._pcr) return 1;
			if(this._pcr < otherId._pcr) return -1;
			if(this._frequency > otherId._frequency) return 1;
			if(this._frequency < otherId._frequency) return -1;
			int result = this._deviceId.CompareTo(otherId._deviceId);
			if(result != 0) return result;
			if(this._deviceId.Type == TunerType.IPTV)
			{
				if(this._multicastGroupIP > otherId._multicastGroupIP) return 1;
				if(this._multicastGroupIP < otherId._multicastGroupIP) return -1;
			}
			return 0;
		}
		#endregion

		public override int GetHashCode()
		{
			return ((int)(_tid ^ _sid ^ _satPosition ^ _lnb ^ _nid ^ _pcr)) ^ (int)_frequency ^ 
				_deviceId.GetHashCode() ^ (int)_multicastGroupIP;
		}

		public override bool Equals(object obj)
		{
			if(obj == null)
				return false;

			if(!(obj is ChannelId))
				return false;

			return this.CompareTo(obj) == 0;
		}

		public bool Equals(ChannelId channelId)
		{
			if((object)channelId == null)
				return false;

			return this.CompareTo(channelId) == 0;
		}

		public static bool operator ==(ChannelId a, ChannelId b)
		{
			if(object.ReferenceEquals(a, b))
				return true;

			if(((object)a == null) || ((object)b == null))
				return false;

			return a.Equals(b);
		}

		public static bool operator !=(ChannelId a, ChannelId b)
		{
			return !(a == b);
		}
	}
}
